package com.yzd.learnjava.generics;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import com.yzd.learnjava.util.Print;

interface Combiner<T> {T combine(T x , T y);}
interface UnaryFuction<R, T>{ R function(T x);}
interface Collector<T> extends UnaryFuction<T, T>{T result();}
interface UnaryPredicate<T>{boolean test(T x);}


public class Functional {
	public static <T> T reduce(Iterable<T> seq, Combiner<T> combiner) {
		Iterator<T> iterator = seq.iterator();
		if(iterator.hasNext()) {
			T result = iterator.next();
			while(iterator.hasNext()) {
				result = combiner.combine(result, iterator.next());
			}
			return result;
		}
		return null;
	}
	
	public static <T> Collector<T> forEach(Iterable<T> seq, Collector<T> func){
		for(T t : seq) {
			func.function(t);
		}
		return func;
	}
	
	public static <R, T> List<R> transform(Iterable<T> seq, UnaryFuction<R, T> func){
		List<R> result = new ArrayList<R>();
		for(T t : seq) {
			result.add(func.function(t));
		}
		
		return result;
	}
	
	public static <T> List<T> filter(Iterable<T> seq, UnaryPredicate<T> pred){
		List<T> result = new ArrayList<T>();
		for(T t : seq) {
			if(pred.test(t)) {
				result.add(t);
			}
		}
		return result;
	}
	
	static class IntegerAdder implements Combiner<Integer>{
		public Integer combine(Integer x, Integer y) {
			return x + y;
		}
	}
	
	static class IntegerSubstracter implements Combiner<Integer>{
		public Integer combine(Integer x, Integer y) {
			return x - y;
		}
	}
	
	static class BigDecimalAdder implements Combiner<BigDecimal>{
		public BigDecimal combine(BigDecimal x, BigDecimal y) {
			return x.add(y);
		}
	}
	
	static class BigIntegerAdder implements Combiner<BigInteger>{
		public BigInteger combine(BigInteger x, BigInteger y) {
			return x.add(y);
		}
	}
	
	static class AtomicLongAdder implements Combiner<AtomicLong>{
		public AtomicLong combine(AtomicLong x, AtomicLong y) {
			return new AtomicLong(x.addAndGet(y.get()));
		}
	}
	
	
	static class BigDecimalUlp implements UnaryFuction<BigDecimal, BigDecimal>{
		public BigDecimal function(BigDecimal x) {
			return x.ulp();
		}
	}
	
	static class GreaterThan<T extends Comparable<T>> implements UnaryPredicate<T>{
		private T bound;
		public GreaterThan(T bound) {
			this.bound = bound;
		}
		public boolean test(T x) {
			return x.compareTo(bound) > 0;
		}
	}
	
	static class MutiplyingIntegerCollector implements Collector<Integer>{
		private Integer val = 1;
		public Integer function(Integer x) {
			val *= x ;
			return val;
		}
		public Integer result() {
			return val;
		}
	}
	
	
	public static void main(String[] args) {
		List<Integer> li = Arrays.asList(1,2,3,4,5,6,7);
		Integer result = reduce(li, new IntegerAdder());
		Print.print("reduce, IntegerAdder; result : " + result);
		
		result = reduce(li, new IntegerSubstracter());
		Print.print("reduce, IntegerSubstracter; result : " + result);
		
		Print.print("filter, GreaterThan; result : " + filter(li, new GreaterThan<Integer>(4)));
		
		Print.print("forEach, MutiplyingIntegerCollector; result : " + forEach(li, new MutiplyingIntegerCollector()).result());
		Print.print("forEach, MutiplyingIntegerCollector; result : " + forEach(filter(li, new GreaterThan<Integer>(4)), new MutiplyingIntegerCollector()).result());
		
		
		MathContext mc = new MathContext(7);
		List<BigDecimal> lbd = Arrays.asList(new BigDecimal(1.1, mc),new BigDecimal(2.2, mc),
				new BigDecimal(3.3, mc),new BigDecimal(4.4, mc));
		
		BigDecimal bd = reduce(lbd, new BigDecimalAdder());
		Print.print("reduce, BigDecimalAdder; result : " + bd);
		
		Print.print("filter, GreaterThan; result : " + filter(lbd, new GreaterThan<BigDecimal>(new BigDecimal(3))));
		
		List<BigInteger> lbi = new ArrayList<BigInteger>();
		BigInteger bi = BigInteger.valueOf(11);
		for(int i = 0 ; i < 11; i++) {
			lbi.add(bi);
			bi = bi.nextProbablePrime();
		}
		Print.print("BigInteger List:" + lbi);
		
		BigInteger rbi = reduce(lbi, new BigIntegerAdder());
		Print.print("reduce, BigIntegerAdder; result : " + rbi);
		Print.print("BigInteger.isProbablePrime(5) : " + rbi.isProbablePrime(5));
		
		
		List<AtomicLong> lal = Arrays.asList(new AtomicLong(11), new AtomicLong(47),
				new AtomicLong(74), new AtomicLong(133));
		AtomicLong ral = reduce(lal, new AtomicLongAdder());
		Print.print("reduce, AtomicLongAdder; result : " + ral);
		
		Print.print("BigDecimalUlp , " + transform(lbd, new BigDecimalUlp()));
	}
	
}

